package io.github.xyzboom.cj

import io.github.xyzboom.cj.data.DebugExpression
import io.github.xyzboom.cj.data.RawCjNode
import io.github.xyzboom.cj.data.VarId
import io.github.xyzboom.cj.data.currentDebugData
import vscode.CancellationToken
import vscode.Event
import vscode.EventEmitter
import vscode.TextDocumentContentProvider
import vscode.Uri
import vscode.window.showInformationMessage

object CjTextDocProvider : TextDocumentContentProvider {

    fun uriForEval(exprEvalStr: String): Uri {
        return Uri.parse("${cj_visual_scheme}:${cj_visual_mode_eval}/${exprEvalStr}")
    }

    // key: Uri String, value: timeOut object in ts
    private val timerIds = HashMap<String, dynamic>()

    /**
     * set a timer to refresh document, if an old timer is not started, cancel the old one.
     */
    fun delayRefreshOnUri(uri: Uri) {
        val uriStr = uri.toString(false)
        val oldTimer = timerIds[uriStr]
        if (oldTimer != null) {
            kotlinx.browser.window.clearTimeout(oldTimer)
        }
        val newTimer = kotlinx.browser.window.setTimeout({
            emitter.fire(uri)
            timerIds.remove(uriStr)
        }, 1500)
        timerIds[uriStr] = newTimer
    }

    val emitter = EventEmitter<Uri>()

    fun handleExpr(expr: DebugExpression): String {
        val result = LineBasedStringBuilder(expr.beginLine!!, expr.endLine!!)
        val rawNode = currentDebugData!!.buildRawCjNode(VarId(expr.id))
            ?: return "/*Waiting for refresh or you can refresh manually*/"
        fun visitNodeFirst(node: RawCjNode) {
            for (child in node.children) {
                val childVar = child.variable
                if (childVar.name == "line") {
                    val parent = child.parent ?: continue
                    if (parent.variable.name == "pos") {
                        val gParent = parent.parent ?: continue
                        gParent.asDynamic().line = childVar.value
                    }
                } else if (childVar.name == "column") {
                    val parent = child.parent ?: continue
                    if (parent.variable.name == "pos") {
                        val gParent = parent.parent ?: continue
                        gParent.asDynamic().column = childVar.value
                    }
                } else if (childVar.name == "value") {
                    val parent = child.parent ?: continue
                    parent.asDynamic().cjValue = childVar.value
                }
                visitNodeFirst(child)
            }
        }
        visitNodeFirst(rawNode)

        fun visitNodeSecond(node: RawCjNode) {
            for (child in node.children) {
                val line = node.asDynamic().line
                val column = node.asDynamic().column
                val value = node.asDynamic().cjValue
                if (line != undefined && column != undefined && value != undefined) {
                    devPrintln("$line, $column, $value")
                    line.toString().toIntOrNull() ?: continue
                    column.toString().toIntOrNull() ?: continue
                    result.set(line, column, JSON.parse(value))
                }
                visitNodeSecond(child)
            }
        }

        visitNodeSecond(rawNode)

        return result.toString()
    }

    override var onDidChange: Event<Uri>?
        get() = emitter.event
        set(value) {
            super.onDidChange = value
        }

    override fun provideTextDocumentContent(uri: Uri, token: CancellationToken): String? {
        devPrintln("provideTextDocumentContent for: ${uri.toString(true)}")
        val strings = uri.path.split("/")
        val mode = strings[0]
        val id = strings[1]
        val debugData = currentDebugData ?: return null
        val debugSession = debugData.session
        if (mode == cj_visual_mode_eval) {
            val expr = debugData.exprMap[id]
            if (expr != null && expr.ready) {
                return handleExpr(expr)
            } else {
                showInformationMessage("internal error: the expression should be ready, please report this issue.")
            }
        }
        return null
    }

    @Suppress("unused")
    fun dispose() {
        devPrintln("dispose")
    }
}